#ifndef _AMERICO_FUSTRUM_H_
#define _AMERICO_FUSTRUM_H_

#define _USE_MATH_DEFINES
#include <math.h>

#include "../Vector.h"
#include "../staticarray.h"
#include "Plane3D.h"

enum FrustumIntersection
{
	FRUSTUM_IN,
	FRUSTUM_OUT,
	FRUSTUM_INTERSECT
};

enum FrustumPlanes
{
	F_NEAR,
	F_FAR,
	F_TOP,
	F_BOTTOM,
	F_LEFT,
	F_RIGHT
};
const unsigned int NUMBER_OF_PLANES = F_RIGHT+1;
enum FrustumPoints
{
	F_NEAR_TOP_LEFT,
	F_NEAR_TOP_RIGHT,
	F_NEAR_BOTTOM_LEFT,
	F_NEAR_BOTTOM_RIGHT,
	F_FAR_TOP_LEFT,
	F_FAR_TOP_RIGHT,
	F_FAR_BOTTOM_LEFT,
	F_FAR_BOTTOM_RIGHT
};
const unsigned int NUMBER_OF_POINTS = F_FAR_BOTTOM_RIGHT+1;

struct FustrumSetup
{
	Vector2i res;
	Vector3f pos;
	Vector3f rot;
	float fov;
	float nearPlane;
	float farPlane;

	FustrumSetup() : res(0,0), pos(0.0f,0.0f,0.0f), rot(0.0f,0.0f,0.0f), fov(static_cast<float>(M_PI_2)), nearPlane(1.0f), farPlane(100.0f) {}
};

class Fustrum
{
// #pragma message(Reminder "REMOVE THIS RASMUS")
// 	friend class PortalFrustum;
// 	/////
private:
	Utils::StaticArray<Plane3D, NUMBER_OF_PLANES> myPlanes;
	Utils::StaticArray<Vector3f, NUMBER_OF_POINTS> myPoints;
	Vector3f myPosition;
	float myFOV;
	float myRatio;
	float myNearDistance;
	float myFarDistance;
	Vector3f myNearPoint;
	Vector3f myFarPoint;

	Matrix33f myToWorld;
	Vector3f myToWorldVector;

	Utils::StaticArray<Vector3f, NUMBER_OF_PLANES> myCachedNormals;
	Utils::StaticArray<float, NUMBER_OF_PLANES> myCachedDistances;
	Utils::StaticArray<Vector3f, NUMBER_OF_POINTS> myCachedBoxPoints;

public:
	Fustrum();
	~Fustrum();

	void Init(const FustrumSetup &aSetup);

	
	FrustumIntersection PointVsFrustum(const Vector3f &aPoint) const;
	FrustumIntersection SphereVsFrustum(const Intersection::Sphere &aSphere) const;
	FrustumIntersection AabbVsFrustum(const Intersection::AABB &anAabb) const;

	void GetTransformation(Matrix44f &aMatrix);
	void SetTransformation(const Matrix44f &aMatrix);

	void RecalculateFromPoints();

	inline float FarPlane() const { return myFarDistance; }
	inline float NearPlane() const { return myNearDistance; }
	inline float FOV() const { return myFOV; }
	inline float Ratio() const { return myRatio; }
	inline Utils::StaticArray<Plane3D, NUMBER_OF_PLANES> &Planes() { return myPlanes; }
	inline Plane3D &Planes(const unsigned int &anIndex) { return myPlanes[anIndex]; }
	inline const Utils::StaticArray<Plane3D, NUMBER_OF_PLANES> &Planes() const { return myPlanes; }
	inline const Plane3D &Planes(const unsigned int &anIndex) const { return myPlanes[anIndex]; }
	inline Utils::StaticArray<Vector3f, NUMBER_OF_PLANES> &Normals() { return myCachedNormals; }
	inline Vector3f &Normals(const unsigned int &anIndex) { return myCachedNormals[anIndex]; }
	inline Utils::StaticArray<float, NUMBER_OF_PLANES> &Distances() { return myCachedDistances; }
	inline float &Distances(const unsigned int &anIndex) { return myCachedDistances[anIndex]; }
	//inline Utils::StaticArray<Vector3f, NUMBER_OF_PLANES> &Points() { return myCachedPoints; }
	//inline Vector3f &Points(const unsigned int &anIndex) { return myCachedPoints[anIndex]; }
	inline Utils::StaticArray<Vector3f, NUMBER_OF_POINTS> &BoxPoints() { return myCachedBoxPoints; }
	inline const Utils::StaticArray<Vector3f, NUMBER_OF_POINTS> &BoxPoints() const { return myCachedBoxPoints; }
	inline Vector3f &BoxPoints(const unsigned int &anIndex) { return myCachedBoxPoints[anIndex]; }
	inline const Vector3f &BoxPoints(const unsigned int &anIndex) const { return myCachedBoxPoints[anIndex]; }

	Vector3f ToFrustumSpace(const Vector3f &aVector);
	Vector3f ToWorldSpace(const Vector3f &aVector);

	void Test(Matrix44f& aOrientation, Matrix44f& aProjection, Matrix44f& aRealOrientation);

private:
	void Calculate();

	void CacheFrustrum();

};

#endif